package com.jackstraw.jack_base.datasource.database.generator.service.impl;

import com.jackstraw.jack_base.datasource.common.aspect.annotation.DataSourceNameCut;
import com.jackstraw.jack_base.datasource.common.entity.enums.DataSourceTypeEnum;
import com.jackstraw.jack_base.datasource.database.generator.dao.GeneratorDaoRouting;
import com.jackstraw.jack_base.datasource.database.generator.entity.ColumnEntity;
import com.jackstraw.jack_base.datasource.database.generator.entity.GeneratorType;
import com.jackstraw.jack_base.datasource.database.generator.entity.GeneratorVo;
import com.jackstraw.jack_base.datasource.database.generator.entity.TableEntity;
import com.jackstraw.jack_base.datasource.database.generator.service.GeneratorService;
import com.jackstraw.jack_base.datasource.database.generator.util.MyBatisUtil;
import com.jackstraw.jack_base.util.DateTimeUtil;
import com.jackstraw.jack_base.util.FileUtil;
import com.jackstraw.jack_base.util.ObjectUtil;
import com.jackstraw.jack_base.util.StringUtil;
import com.jackstraw.jack_base.util.entity.enums.DataBaseEnum;
import com.jackstraw.jack_base.util.exception.BadConfigException;
import org.apache.commons.configuration2.Configuration;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.WordUtils;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.springframework.beans.factory.annotation.Autowired;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

@DataSourceNameCut(dataSourceName = "dataBaseName1", dataSourceTypeEnum = DataSourceTypeEnum.database)
public class GeneratorServiceImpl implements GeneratorService {

    @Autowired
    private GeneratorDaoRouting generatorDaoRouting;

    /**
     * 表名转换成Java类名
     */
    public static String tableNameToJava(String tableName, String tablePrefix) {
        if (StringUtils.isNotBlank(tablePrefix)) {
            tableName = tableName.startsWith(tablePrefix) ? tableName.replaceFirst(tablePrefix, "") : tableName;
        }
        return columnToJava(tableName);
    }

    /**
     * 列名转换成Java属性名
     */
    public static String columnToJava(String columnName) {
        return WordUtils.capitalizeFully(columnName, new char[]{'_'}).replace("_", "");
    }

    @Override
    public List<TableEntity> queryList(List<String> tableNameList, DataBaseEnum dataBaseEnum) {
        List<Map<String, Object>> maps = generatorDaoRouting.getGeneratorDao(dataBaseEnum).queryList(tableNameList);
        return maps.stream().map(map -> {
            TableEntity tableEntity = new TableEntity();
            tableEntity.setTableName((String) map.get("tableName"));
            tableEntity.setCreateTime((Date) map.get("createTime"));
            tableEntity.setComments((String) map.get("tableComment"));
            return tableEntity;
        }).collect(Collectors.toList());
    }

    @Override
    public TableEntity queryTable(String tableName, DataBaseEnum dataBaseEnum) {
        Map<String, Object> map = generatorDaoRouting.getGeneratorDao(dataBaseEnum).queryTable(tableName);
        TableEntity tableEntity = new TableEntity();
        tableEntity.setTableName((String) map.get("tableName"));
        tableEntity.setCreateTime((Date) map.get("createTime"));
        tableEntity.setComments((String) map.get("tableComment"));
        return tableEntity;
    }

    @Override
    public void queryColumns(TableEntity tableEntity, DataBaseEnum dataBaseEnum) {
        List<Map<String, Object>> columns = generatorDaoRouting.getGeneratorDao(dataBaseEnum).queryColumns(tableEntity.getTableName());

        //配置信息
        Configuration config = MyBatisUtil.getConfig();
        for (Map<String, Object> column : columns) {
            ColumnEntity columnEntity = new ColumnEntity();
            columnEntity.setColumnName((String) column.get("columnName"));
            columnEntity.setDataType((String) column.get("dataType"));
            columnEntity.setComments((String) column.get("columnComment"));
            columnEntity.setExtra((String) column.get("extra"));
            //属性名称(第一个字母小写)，如：user_name => userName
            String fieldName = StringUtil.toCamelCase(columnEntity.getColumnName());
            columnEntity.setFieldName(fieldName);
            //userName => UserName
            String attrName = StringUtil.captureName(fieldName);
            columnEntity.setAttrName(attrName);

            //列的数据类型，转换成Java类型
            String attrType = config.getString(columnEntity.getDataType(), "unknowType");
            columnEntity.setAttrType(attrType);

            //是否主键
            if ("PRI".equalsIgnoreCase((String) column.get("columnKey"))) {
                tableEntity.getPkS().add(columnEntity);
                if ("BigDecimal".equals(attrType)) {
                    tableEntity.setHasKeyBigDecimal(Boolean.TRUE);
                }
                if ("Date".equals(attrType)) {
                    tableEntity.setHasKeyDate(Boolean.TRUE);
                }
            } else {
                if ("BigDecimal".equals(attrType)) {
                    tableEntity.setHasBigDecimal(Boolean.TRUE);
                }
                if ("Date".equals(attrType)) {
                    tableEntity.setHasDate(Boolean.TRUE);
                }
                tableEntity.getColumns().add(columnEntity);
            }

            tableEntity.getAllColumns().add(columnEntity);
        }
        //
        //查询主键信息
        List<String> pkColumns = this.queryPkColumns(tableEntity, dataBaseEnum);
        Map<String, ColumnEntity> columnEntityMap = tableEntity.getPkS().stream().collect(Collectors.toMap(ColumnEntity::getColumnName, columnEntity -> columnEntity));
        List<ColumnEntity> pkS = new ArrayList<>();
        pkColumns.stream().forEach(pkColumn -> pkS.add(columnEntityMap.get(pkColumn)));
        tableEntity.getPkS().clear();
        tableEntity.setPkS(pkS);
    }

    @Override
    public List<String> queryPkColumns(TableEntity tableEntity, DataBaseEnum dataBaseEnum) {
        List<Map<String, Object>> maps = generatorDaoRouting.getGeneratorDao(dataBaseEnum).queryPkColumns(tableEntity.getTableName());
        return maps.stream().map(map -> (String) map.get("columnName")).collect(Collectors.toList());
    }

    @Override
    public byte[] generatorCode(GeneratorVo generatorVo) {

        DataBaseEnum dataBaseEnum = generatorVo.getDataBaseEnum();
        if (ObjectUtil.ObjectUtilByLang3.isEmpty(dataBaseEnum)) {
            dataBaseEnum = generatorDaoRouting.getSchema();
            generatorVo.setDataBaseEnum(dataBaseEnum);

        }

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ZipOutputStream zip = new ZipOutputStream(outputStream);

        List<TableEntity> tableEntities = this.queryList(generatorVo.getTableNameList(), dataBaseEnum);
        //设置velocity资源加载器
        Properties prop = new Properties();
        prop.put("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
        Velocity.init(prop);


        //获取模板列表
        List<String> templates = generatorTemplates(generatorVo.getGeneratorType(), dataBaseEnum);

        for (TableEntity tableEntity : tableEntities) {
            //表名转换成Java类名
            String className = tableNameToJava(tableEntity.getTableName(), generatorVo.getTablePrefix());
            tableEntity.setClassName(className);
            tableEntity.setClassname(StringUtils.uncapitalize(className));
            this.queryColumns(tableEntity, dataBaseEnum);
            //封装模板数据
            Map<String, Object> map = new HashMap<>();
            map.put("tableName", tableEntity.getTableName());
            map.put("comments", tableEntity.getComments());
            map.put("pkS", tableEntity.getPkS());
            map.put("primaryKeyConditions", tableEntity.primaryKeyConditions());
            map.put("primaryKeyRecordConditions", tableEntity.primaryKeyRecordConditions());
            map.put("allColumns", tableEntity.getAllColumns());
            map.put("className", tableEntity.getClassName());
            map.put("classname", tableEntity.getClassname());
            map.put("columns", tableEntity.getColumns());
            map.put("hasBigDecimal", tableEntity.getHasBigDecimal());
            map.put("hasDate", tableEntity.getHasDate());
            map.put("hasKeyBigDecimal", tableEntity.getHasKeyBigDecimal());
            map.put("hasKeyDate", tableEntity.getHasKeyDate());
            map.put("version", generatorVo.getVersion());
            map.put("packageDao", generatorVo.getPackageDao());
            map.put("packageEntity", generatorVo.getPackageEntity());
            map.put("packageService", generatorVo.getPackageService());
            map.put("dataSourceName", generatorVo.getDataSourceName());

            map.put("author", generatorVo.getAuthor());
            map.put("email", generatorVo.getEmail());
            map.put("datetime", DateTimeUtil.format(new Date(), DateTimeUtil.NORM_DATETIME_PATTERN));
            map.put("date", DateTimeUtil.format(new Date(), DateTimeUtil.NORM_DATETIME_PATTERN));
            map.put("dataBaseEnum", dataBaseEnum.name());


            if (tableEntity.getPkS().size() == 1) {
                ColumnEntity columnEntity = tableEntity.getPkS().get(0);
                map.put("idFieldName", columnEntity.getFieldName());
                map.put("idFieldAttrType", columnEntity.getAttrType());
            }


            VelocityContext context = new VelocityContext(map);

            for (String template : templates) {
                //渲染模板
                StringWriter sw = new StringWriter();
                Template tpl = Velocity.getTemplate(template, "UTF-8");
                tpl.merge(context, sw);
                String fileName = getFileName(template, className, generatorVo);
                try {
                    //添加到zip
                    zip.putNextEntry(new ZipEntry(fileName));
                    IOUtils.write(sw.toString(), zip, "UTF-8");
                    IOUtils.closeQuietly(sw);
                    zip.closeEntry();
                } catch (IOException e) {
                    throw new BadConfigException("001", "渲染模板失败，表名[{}]", e, tableEntity.getTableName());
                }
            }
        }
        IOUtils.closeQuietly(zip);
        return outputStream.toByteArray();
    }


    public List<String> generatorTemplates(GeneratorType generatorType, DataBaseEnum dataBaseEnum) {
        switch (generatorType) {
            case mybatis:
                return getMyBatisTemplates(dataBaseEnum);
            case mybatis_plus:
                return getMyBatisPlusTemplates(dataBaseEnum);
            case jpa:
                return getJPATemplates(dataBaseEnum);
        }
        return new ArrayList<>();
    }

    public List<String> getJPATemplates(DataBaseEnum dataBaseEnum) {
        List<String> templates = new ArrayList<>();
        templates.add("jpa/template/Bo.java.vm");
        templates.add("jpa/template/Dao.java.vm");
        templates.add("jpa/template/Dto.java.vm");
        switch (dataBaseEnum) {
//            case MySQL:
//                templates.add("jpa/template/MySQLPo.java.vm");
//                break;
//            case Oracle:
//                templates.add("jpa/template/OraclePo.java.vm");
//                break;
            default:
                templates.add("jpa/template/Po.java.vm");
        }
        templates.add("jpa/template/Service.java.vm");
        templates.add("jpa/template/ServiceImpl.java.vm");
        templates.add("jpa/template/Vo.java.vm");
        return templates;
    }

    public List<String> getMyBatisPlusTemplates(DataBaseEnum dataBaseEnum) {
        List<String> templates = new ArrayList<>();
        templates.add("mybatis-plus/template/Bo.java.vm");
        templates.add("mybatis-plus/template/Dao.java.vm");
        templates.add("mybatis-plus/template/Dto.java.vm");
        templates.add("mybatis-plus/template/KeyPo.java.vm");
        templates.add("mybatis-plus/template/Mapper.xml.vm");
        templates.add("mybatis-plus/template/Po.java.vm");
        templates.add("mybatis-plus/template/Service.java.vm");
        templates.add("mybatis-plus/template/ServiceImpl.java.vm");
        templates.add("mybatis-plus/template/Vo.java.vm");
        return templates;
    }

    public List<String> getMyBatisTemplates(DataBaseEnum dataBaseEnum) {
        List<String> templates = new ArrayList<>();
        templates.add("mybatis/template/Bo.java.vm");
        templates.add("mybatis/template/Dao.java.vm");
        templates.add("mybatis/template/Dto.java.vm");
        templates.add("mybatis/template/KeyPo.java.vm");
        switch (dataBaseEnum) {
            case Oracle:
                templates.add("mybatis/template/oracle/Mapper.xml.vm");
                break;
            default:
                templates.add("mybatis/template/Mapper.xml.vm");
        }
        templates.add("mybatis/template/Po.java.vm");
        templates.add("mybatis/template/PoExample.java.vm");
        templates.add("mybatis/template/Service.java.vm");
        templates.add("mybatis/template/ServiceImpl.java.vm");
        templates.add("mybatis/template/Vo.java.vm");
        return templates;
    }


    /**
     * 获取文件名
     */
    public String getFileName(String template, String className, GeneratorVo generatorVo) {

        String packagePath = generatorVo.getMainOrTest() + FileUtil.separator + "java" + FileUtil.separator;

        if (template.endsWith("Bo.java.vm")) {
            return packagePath + generatorVo.getPackageEntity().replace(".", FileUtil.separator) + FileUtil.separator + "entity" + FileUtil.separator + "bo" + FileUtil.separator + className + "Bo.java";
        }

        if (template.endsWith("KeyPo.java.vm")) {
            return packagePath + generatorVo.getPackageEntity().replace(".", FileUtil.separator) + FileUtil.separator + "entity" + FileUtil.separator + "po" + FileUtil.separator + className + "KeyPo.java";
        }

        if (template.endsWith("Po.java.vm")) {
            return packagePath + generatorVo.getPackageEntity().replace(".", FileUtil.separator) + FileUtil.separator + "entity" + FileUtil.separator + "po" + FileUtil.separator + className + "Po.java";
        }

        if (template.endsWith("PoExample.java.vm")) {
            return packagePath + generatorVo.getPackageEntity().replace(".", FileUtil.separator) + FileUtil.separator + "entity" + FileUtil.separator + "po" + FileUtil.separator + className + "PoExample.java";
        }

        if (template.endsWith("Dto.java.vm")) {
            return packagePath + generatorVo.getPackageEntity().replace(".", FileUtil.separator) + FileUtil.separator + "entity" + FileUtil.separator + "dto" + FileUtil.separator + className + "Dto.java";
        }

        if (template.endsWith("Vo.java.vm")) {
            return packagePath + generatorVo.getPackageEntity().replace(".", FileUtil.separator) + FileUtil.separator + "entity" + FileUtil.separator + "vo" + FileUtil.separator + className + "Vo.java";
        }

        if (template.endsWith("Service.java.vm")) {
            return packagePath + generatorVo.getPackageService().replace(".", FileUtil.separator) + FileUtil.separator + "service" + FileUtil.separator + className + "Service.java";
        }

        if (template.endsWith("ServiceImpl.java.vm")) {
            return packagePath + generatorVo.getPackageService().replace(".", FileUtil.separator) + FileUtil.separator + "service" + FileUtil.separator + "impl" + FileUtil.separator + className + "ServiceImpl.java";
        }

        if (template.endsWith("Dao.java.vm")) {
            return packagePath + generatorVo.getPackageDao().replace(".", FileUtil.separator) + FileUtil.separator + "dao" + FileUtil.separator + "database" + FileUtil.separator + className + "Dao.java";
        }

        if (template.endsWith("Mapper.xml.vm")) {
            return generatorVo.getMainOrTest() + FileUtil.separator + "resources" + FileUtil.separator + "mapper" + FileUtil.separator + className + "Mapper.xml";
        }

        return null;
    }


}
